#pending
# 1: multiplying/dividing a nakshatra with a scalar

import math

from math import *

class Angle:
    sign=1
    degrees=0
    minutes=0
    seconds=0
    __name__ = "";

    def __init__ (self, degrees=0.0, minutes=0.0, seconds=0.0, sign=1, simplify=True):
        self.__name__ = "Angle"
        self.degrees = degrees
        self.minutes = minutes
        self.seconds = seconds*1.0
        self.sign=sign
        if (simplify):
            self.normalize()

    def __gt__ (self, other):
        '''
        compare absolute value of two angles
        '''
        if (self.degrees>other.degrees):
            return True
        elif (self.degrees==other.degrees):
            if (self.minutes>other.minutes):
                return True
            elif (self.minutes==other.minutes):
                if (self.seconds>other.seconds):
                    return True
                else:
                    return False
            else:
                return False
        else:
            return False

    def __eq__ (self, other):
        threshold=0.1
        return ( (self.degrees==other.degrees) and (self.minutes==other.minutes) and abs(self.seconds-other.seconds)<=threshold );


    def __ge__ (self, other):
        return (self>other or self==other);
    
    def __lt__ (self, other):
        return other>self;
    
    def __le__ (self, other):
        return (self<other or self==other);

    def __ne__ (self, other):
        return not (self==other);

    def __add__ (self, other):
        op = Angle(self.degrees+other.degrees, self.minutes+other.minutes, self.seconds+other.seconds);
        return op;

    def __iadd__ (self, other):
        self = self + other;
        return self;

    def __isub__ (self, other):
        self = self - other;
        return self;

    def __imul__ (self, other):
        self = self * other;
        return self;

    def __mul__ (self, n):
        '''
        Angle * n
        n - an integer
        '''
        op = Angle();

        for i in range(abs(n)):
            op += self;
        
        if (n>=0):
            return op;
        else:
            return Angle(op.degrees, op.minutes, op.seconds,-1); 

    def __rmul__ (self, n):
        '''
        n * Angle
        '''
        return (self*n);

    def get_seconds(self):
        return ((self.degrees*60+self.minutes)*60+self.seconds);

    def get_minutes(self):
        return self.get_seconds()/60.0;

    def get_degrees(self):
        return self.get_minutes()/60.0;

    def __div__ (self, n):
        '''
        Angle / n
        '''
        if (isinstance(n,Angle)):
            if(n.__name__=="Angle"):
                divisor = n.get_seconds()*n.sign;
                assert(divisor!=0);
                return (self.get_seconds()/divisor);

        divisor=n*1.0; #convert n to a float
        assert(divisor!=0);

        op = Angle(self.degrees, self.minutes, self.seconds, self.sign/(1 if divisor>0 else -1));

        op.denormalize();
        divisor=abs(divisor);
        op.seconds /= divisor;
        op.normalize();
        
        return op;
    
            
    def __neg__ (self):
        op = Angle(self.degrees, self.minutes, self.seconds, -1*self.sign);
        return op;

    def __sub__ (self, other):
        return (self + (-other));

    def denormalize(self):
        self.minutes += self.degrees*60;
        self.seconds += self.minutes*60;
        self.degrees = self.minutes = 0;
        return;

    def normalize(self):

        if (self.sign==-1):
            self.degrees*=-1;
            self.minutes*=-1;
            self.seconds*=-1;
            
            self.degrees+=359;
            self.minutes+=59;
            self.seconds+=60;
    
            self.sign=1;

        #avoidfrac-to avoid fractions in degrees, minutes and seconds
        self.minutes += (self.degrees - round(self.degrees)) * 60
        self.degrees = round(self.degrees)

        self.seconds += (self.minutes - round(self.minutes)) * 60
        self.minutes = round(self.minutes)

        self.seconds = round(self.seconds) # as of now, not allowing fractions in seconds
        #avoidfrac - end
        
        if(self.seconds>=60):
            self.minutes = self.minutes + floor(self.seconds/60);
            self.seconds = self.seconds%60;

        if(self.minutes>=60):
            self.degrees = self.degrees + floor(self.minutes/60);
            self.minutes = self.minutes%60;

        if(self.degrees>=360):
            self.degrees = self.degrees % 360;

    def lies_between (self, A, B):
        '''
        checks if self lies in between A and B, when going from A to B in anti-clockwise
        '''
        return ( (B-A)>=(self-A) and (B-A)>=(B-self));

    def display(self):
        if (self.sign==1):
            sign_chr = "+";
        else:
            sign_chr = "-";

        return (sign_chr+str(self.degrees)+u'\u00B0'+str(self.minutes)+u'\u2032'+str(self.seconds)+u'\u2033');


    def __str__(self):
        if (self.sign==1):
            sign_chr = "+"
        else:
            sign_chr = "-"

        return (sign_chr+str(self.degrees)+' degrees '+str(self.minutes)+' minutes '+str(self.seconds)+' seconds')

class Arc:
    '''
    both start and end angles are part of the Arc
    '''
    def __init__ (self, start_angle, end_angle):
        self.start_angle = start_angle
        self.end_angle = end_angle

    def __contains__ (self, arc):
        if self.contains_angle(arc.start_angle) and self.contains_angle(arc.end_angle) and (self.end_angle - self.start_angle) >= (arc.end_angle - arc.start_angle):
            return True
        else:
            return False

    def contains_angle (self, ang):
        if ang.lies_between(self.start_angle, self.end_angle):
            return True
        else:
            return False

    def intersects (self, arc):
        '''
        boolean output. True if self interacts with arc
        '''
        if self in arc or arc in self:
            return True

        if self.contains_angle(arc.start_angle) or self.contains_angle(arc.end_angle):
            return True

        return False

    def intersection (self, arc):
        if not self.intersects(arc):
            return None

        return Arc(max(self.start_angle, arc.start_angle), min(self.end_angle, arc.end_angle))

    def __rdiv__ (self, arc):
        '''
        invoked when arc/self
        '''
        if not self.intersects(arc):
            return 0.0

        intersected_arc = self.intersection(arc)

        return (intersected_arc.end_angle - intersected_arc.start_angle)/(self.end_angle - self.start_angle)

class SkyDivision(Arc):
    '''
    both start and end angles are part of the division
    '''
    def __init__ (self, skydivision_scheme, index):
        self.skydivision_scheme = skydivision_scheme
        self.index = index
        self.start_angle = skydivision_scheme.angle_per_division * index
        self.end_angle = skydivision_scheme.angle_per_division * (index + 1) - Angle(seconds=1)

    def __rsub__ (self, div):
        '''
        return number of divisions starting from self to another SkyDivision div
        count from self in positive angular direction till div is reached
        '''
        return (div.index + len(div.skydivision_scheme) - self.index) % len(div.skydivision_scheme)

    def __add__ (self, offset):
        return SkyDivision(self.skydivision_scheme, (self.index+offset)%len(self.skydivision_scheme))

    def __radd__ (self, offset):
        return (self+offset)

    def __str__ (self):
        return self.skydivision_scheme.divisions[self.index]

       
class SkyDivisionScheme:
    '''
    abstract class to support SkyDivisionSchemes like Rasis, Nakshatras etc
    '''
    def __init__ (self, divisions):
        '''
        list of division names as input
        '''
        assert(isinstance(divisions,list))
        self.divisions=divisions
        self.initialize()

    def initialize (self):
        self.no_divisions=len(self.divisions)
        self.angle_per_division=Angle(360.0/self.no_divisions)

    def __len__ (self):
        return self.no_divisions

    def which (self, angle):
        '''
        return the division(index) containing angle
        '''
        return int(angle/self.angle_per_division)
        

class Rasis (SkyDivisionScheme):
    def __init__ (self):
        self.divisions = ['mesha', "vrishabha", "mithuna", "karkataka", "simha", "kanya", "thula", "vrischika", "dhanus", "makara", "kumbha", "meena"]
        self.initialize()

    def __getitem__ (self, index):
        if index<0 or index>=len(self):
            raise IndexError

        return Rasi(self, index)

    def which (self, angle):
        return Rasi(self, SkyDivisionScheme.which(self, angle))

class Nakshatras (SkyDivisionScheme):
    def __init__ (self):
        self.divisions = ['aswini', 'bharani', 'karthigai', 'rohini', 'mrigasheersham', 'thiruvaathirai', 'punarpoosam', 'poosam', 'aayilyam', 'makam', 'pooram', 'uthiram', 'hastham', 'chithirai', 'swaathi', 'visaakam', 'anusham', 'kettai', 'moolam', 'pooradam', 'uthiradam', 'thiruvonam', 'avittam', 'sadayam', 'poorattathi', 'uthirattathi', 'revathi']
        self.sanskrit_names = ['aswini', 'bharani', 'krithika', 'rohini', 'mrigashiras', 'arudra', 'punarvasu', 'pushyami', 'ashlesha', 'makha', 'poorvaphalguni', 'uthrashaada', 'shraavan', 'dhanishta', 'shathabhisha', 'poorvabhadra', 'uthrabadhra', 'revathi']
        self.initialize()
    
    def __getitem__ (self, index):
        if index<0 or index>=len(self):
            raise IndexError

        return Nakshatra(self, index)

    def which (self, angle):
        return Nakshatra(self, SkyDivisionScheme.which(self, angle))
        
class Rasi(SkyDivision):
    pass

class Nakshatra(SkyDivision):
    pass
        
